      1  import java.awt.BorderLayout;

      2  import java.awt.Color;

      3  import java.awt.Graphics;

      4  import java.awt.event.ActionEvent;

      5  import java.awt.event.ActionListener;

      6  import java.awt.event.WindowAdapter;

      7  import java.awt.event.WindowEvent;

      8  import java.io.ByteArrayInputStream;

      9  import java.io.File;

     10  import java.io.FileInputStream;

     11  import java.io.InputStream;

     12  import java.io.IOException;

     13  import java.util.Arrays;

     14  import javax.swing.JButton;

     15  import javax.swing.JComponent;

     16  import javax.swing.JFileChooser;

     17  import javax.swing.JFrame;

     18  import javax.swing.JOptionPane;

     19  import javax.swing.JPanel;

     20  import javax.sound.sampled.AudioFileFormat;

     21  import javax.sound.sampled.AudioSystem;

     22  import javax.sound.sampled.Clip;

     23

     24  /**

     25     This program processes a 16 bit uncompressed mono .WAV file.

     26  */

     27  public class SoundClip

     28  {

     29     private static final int HEADER_SIZE = 44;

     30     private byte[] header = new byte[HEADER_SIZE];

     31     private int[] samples;

     32     private int sampleRate;

     33

     34     public int[] getSampleValues()

     35     {

     36        return samples;

     37     }

     38

     39     public int getSampleRate()

     40     {

     41        return sampleRate;

     42     }

     43

     44     /**

     45        Displays a file chooser for picking a clip.

     46     */

     47     public void pick()

     48     {

     49        JFileChooser chooser = new JFileChooser(".");

     50        if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)

     51        {

     52           load(chooser.getSelectedFile());

     53        }

     54     }

     55

     56     /**

     57        Shows a frame with the sample values and a Play button

     58     */

     59     public void show()

     60     {

     61        if (samples != null)

     62        {

     63           JFrame frame = new SoundClipFrame();

     64           frame.setVisible(true);

     65        }

     66     }

     67

     68     // -----------------------------------------------------------------

     69

     70     /*

     71       The code below processes a file in the WAV format.

     72       You can use this program to manipulate sound files without

     73       reading or understanding the code below.

     74

     75       The code uses Java features that are introduced in later chapters

     76       as well as the internals of the WAV format

     77       (https://ccrma.stanford.edu/courses/422/projects/WaveFormat/)

     78     */

     79

     80     /**

     81        Loads a picture from a given source.

     82        @param source the image source.

     83        @return true if the file was loaded successfully

     84     */

     85     private void load(File source)

     86     {

     87        try

     88        {

     89           InputStream in = new FileInputStream(source);

     90           for (int i = 0; i < HEADER_SIZE; i++)

     91           {

     92              int b = in.read();

     93              if (b == -1)

     94              {

     95                 error("Premature end of file");

     96              }

     97              header[i] = (byte) b;

     98           }

     99

    100           int formatType = getUnsignedInt2(20);

    101           if (formatType != 1)

    102           {

    103              error("Not an uncompressed sound file.");

    104           }

    105           int numChannels = getUnsignedInt2(22);

    106           if (numChannels != 1)

    107           {

    108              error("Not a mono sound file.");

    109           }

    110

    111           sampleRate = getUnsignedInt2(24);

    112

    113           int bitsPerSample = getUnsignedInt2(34);

    114           if (bitsPerSample != 16)

    115           {

    116              error("Not a 16 bit sound file.");

    117           }

    118

    119           // Read data size and allocate data array

    120           int dataSize = getUnsignedInt4(40) / 2; // 2 bytes per data

    121           samples = new int[dataSize];

    122

    123           // Read sound data

    124           for (int i = 0; i < dataSize; i++)

    125           {

    126              samples[i] = getSignedInt2(in);

    127           }

    128        }

    129        catch (Exception ex)

    130        {

    131           error(ex.getMessage());

    132        }

    133     }

    134

    135     private void error(String message)

    136     {

    137        JOptionPane.showMessageDialog(null, message);

    138     }

    139

    140     /**

    141        Gets a byte from the header as an unsigned value.

    142        @param offset the offset from the start of the header

    143        @return the integer

    144     */

    145     private int getUnsignedInt1(int offset)

    146     {

    147        int result = header[offset];

    148        if (result >= 0) { return result; }

    149        else { return result + 256; }

    150     }

    151

    152     /**

    153        Gets an unsigned 4-byte integer from the header

    154        @param offset the offset from the start of the header

    155        @return the integer

    156     */

    157     private int getUnsignedInt4(int offset)

    158     {

    159        int result = 0;

    160        int base = 1;

    161        for (int i = 0; i < 4; i++)

    162        {

    163           result = result + getUnsignedInt1(offset + i) * base;

    164           base = base * 256;

    165        }

    166        return result;

    167     }

    168

    169     /**

    170        Gets an unsigned 2-byte integer from a random access file.

    171        @param in the file

    172        @return the integer

    173     */

    174     private int getUnsignedInt2(int offset)

    175     {

    176        return getUnsignedInt1(offset) + 256 * getUnsignedInt1(offset + 1);

    177     }

    178

    179     /**

    180        Gets a signed 2-byte integer from a random access file.

    181        @param in the file

    182        @return the integer

    183     */

    184     private static int getSignedInt2(InputStream in)

    185        throws IOException

    186     {

    187        int lo = in.read();

    188        int hi = in.read();

    189        int result = lo + 256 * hi;

    190        if (result >= 32768) { result = result - 65536; }

    191        return result;

    192     }

    193

    194     private static int frames;

    195     private static int offsets;

    196

    197     class SoundClipFrame extends JFrame

    198     {

    199        private int[] samples;

    200

    201

    202        public SoundClipFrame()

    203        {

    204           frames++;

    205           offsets++;

    206           samples = Arrays.copyOf(SoundClip.this.samples,

    207              SoundClip.this.samples.length);

    208

    209           addWindowListener(new WindowAdapter()

    210              {

    211                 @Override public void windowClosing(WindowEvent event)

    212                 {

    213                    frames--;

    214                    if (frames == 0) System.exit(0);

    215                 }

    216              });

    217

    218           final int FRAME_WIDTH = 800;

    219           final int FRAME_HEIGHT = 200;

    220           final int OFFSET_WIDTH = 100;

    221

    222           setBounds(offsets * OFFSET_WIDTH, offsets * OFFSET_WIDTH, FRAME_WIDTH, FRAME_HEIGHT);

    223

    224           JComponent component = new JComponent()

    225              {

    226                 public void paintComponent(Graphics graph)

    227                 {

    228                    int increment = samples.length / getWidth();

    229

    230                    final int LARGEST = 32767;

    231                    int x = 0;

    232                    for (int i = 0; i < samples.length; i = i + increment)

    233                    {

    234                       int value = samples[i];

    235                       value = Math.min(LARGEST, value);

    236                       value = Math.max(-LARGEST, value);

    237

    238                       int height = getHeight() / 2;

    239                       int y = height - samples[i] * height / LARGEST;

    240                       graph.drawLine(x, y, x, height);

    241                       x++;

    242                    }

    243                 }

    244              };

    245

    246           add(component);

    247           JPanel panel = new JPanel();

    248           JButton button = new JButton("Play");

    249           button.addActionListener(new ActionListener()

    250              {

    251                 public void actionPerformed(ActionEvent event) { play(); }

    252              });

    253           panel.add(button);

    254           add(panel, BorderLayout.SOUTH);

    255        }

    256

    257        private void play()

    258        {

    259           byte[] out = new byte[HEADER_SIZE + 2 * samples.length];

    260           for (int i = 0; i < HEADER_SIZE; i++)

    261           {

    262              out[i] = header[i];

    263           }

    264           for (int i = 0; i < samples.length; i++)

    265           {

    266              int value = samples[i];

    267              if (value < 0) { value = value + 65536; }

    268              out[HEADER_SIZE + 2 * i] = (byte)(value % 256);

    269              out[HEADER_SIZE + 2 * i + 1] = (byte)(value / 256);

    270           }

    271

    272           try

    273           {

    274              Clip clip = AudioSystem.getClip();

    275              clip.open(AudioSystem.getAudioInputStream(new ByteArrayInputStream(out)));

    276              clip.start();

    277           }

    278           catch (Exception ex)

    279           {

    280              error(ex.getMessage());

    281           }

    282        }

    283     }

    284  }

    285